import java.util.ArrayList;

import static java.lang.System.out;

public final class GameEngine {
    private static final boolean PRINT_MOVES = false;
    private static final boolean PRINT_JUMPERS = false;
    private static final boolean PRINT_TOP_BOTTOM_NBRS = false;
    private static final boolean PRINT_BITS = false;

    private static ArrayList<GameState> blackMoves;
    private static ArrayList<GameState> whiteMoves;
    private static long lastBlackTime;
    private static long lastWhiteTime;
    private static int turnNbr = 1;
    private static boolean continueGame = true;

    public static void runGame(Player black, Player white, Board initBoard)
    {
        blackMoves = new ArrayList<GameState>(60);
        whiteMoves = new ArrayList<GameState>(60);
        lastBlackTime = 0L;
        lastWhiteTime = 0L;
        turnNbr = 1;
        continueGame = true;
        Board board = new Board(initBoard);

        out.println("This is the starting board:");
        printBoard(board);

        debugOutPut(board);

        while (continueGame) {
            // Copy the board so the player can't change it
            Board boardCopy = new Board(board);
            Action action;
            if (board.blackToMove) {
                out.println("Black's turn");
                action = black.getMove(boardCopy, turnNbr);
            } else {
                out.println("White's turn");
                action = white.getMove(boardCopy, turnNbr);
            }

            if (action.type == Actions.QUIT) {
                // End of input stream for a human player
                out.println("Quitting.");
                continueGame = false;
            } else if (action.type == Actions.BACK) {
                if (turnNbr <= 1) {
                    out.println("You can't use the back command on the first"
                                + " move.");
                } else {
                    ArrayList<GameState> history;
                    ArrayList<GameState> otherHistory;
                    if (board.blackToMove) {
                        history = blackMoves;
                        otherHistory = whiteMoves;
                    } else {
                        history = whiteMoves;
                        otherHistory = blackMoves;
                    }
                    GameState blackState = blackMoves.get(blackMoves.size()-1);
                    GameState whiteState = whiteMoves.get(whiteMoves.size()-1);
                    black.setSpentTime(blackState.spentTime);
                    white.setSpentTime(whiteState.spentTime);
                    lastBlackTime = blackState.spentTime;
                    lastWhiteTime = whiteState.spentTime;

                    GameState oldState = history.get(history.size() - 1);
                    Board oldBoard = oldState.board;
                    board = new Board(oldBoard);
                    turnNbr--;

                    blackMoves.remove(blackMoves.size() - 1);
                    whiteMoves.remove(whiteMoves.size() - 1);

                    out.println();
                    out.print("Going back to ");
                    out.print(board.blackToMove ? "black's" : "white's");
                    out.println(" turn number " + turnNbr);
                    float blackTime = lastBlackTime / 1000.0F;
                    float whiteTime = lastWhiteTime / 1000.0F;
                    out.printf("Black has spent %.2f s before this move.\n",
                               blackTime);
                    out.printf("White has spent %.2f s before this move.\n",
                               whiteTime);
                    out.println();

                    printBoard(board);
                }
            } else if (action.type == Actions.MOVE) {
                handleMove(action, board);
            } else {
                out.println("Unknown action: " + action.type.toString());
            }
        }
    }

    public static void handleMove(Action action, Board board) {
        if (action.type != Actions.MOVE) {
            return;
        }

        int[] move = action.move;
        if (move == null || move.length < 2) {
            if (move == null) {
                out.println("move is null.");
            }
            if (move.length < 2) {
                out.println("move has a length < 2.");
            }
            out.println("It seems the current player is unable to come up"
                        + " with a valid move.");
            out.println("This might be because the player have no valid"
                        + " moves to make\n"
                        + " or because the program is faulty.");
            continueGame = false;
        } else if (!board.isLegalMove(move)) {
            out.println("Are you trying to pass that off as a valid move?"
                        + " You're quite mad indeed!");
        } else {
            Board oldBoard = new Board(board);
            if (board.blackToMove) {
                blackMoves.add(new GameState(oldBoard, lastBlackTime));
                lastBlackTime = action.time;
            } else {
                whiteMoves.add(new GameState(oldBoard, lastWhiteTime));
                lastWhiteTime = action.time;
            }

            board.makeMove(move);
            board.blackToMove = !board.blackToMove;

            out.println();
            out.println("Here is the board with move made:");
            printBoard(board);

            if (!board.isSane()) {
                out.println("Oh dear. It seems like the board is in an"
                            + " unappropriate state of affairs.");
            }

            ArrayList<Board> moves = board.generateMoves();
            // If no moves possible, the current player loses
            if(moves.isEmpty()) {
                String loser, winner;
                if(board.blackToMove) {
                    loser = "black";
                    winner = "white";
                } else {
                    loser = "white";
                    winner = "black";
                }
                out.println("Oh dear. I'm afraid that " + loser
                            + " has lost the game.");
                out.println("The winner is: " + winner);

                continueGame = false;
            } else {
                debugOutPut(board);
            }

            if (board.blackToMove) {
                turnNbr++;
            }
        }
    }

    public static void debugOutPut(Board board) {
        ArrayList<Board> moves;
        if (PRINT_MOVES) {
            moves = board.generateMoves();
            out.printf("There are %d possible moves, here they are:\n",
                       moves.size());
            for (Board m : moves) {
                printBoard(m);
            }
        }

        int jumpers;
        Board j;
        if (PRINT_JUMPERS) {
            jumpers = board.mustJump();
            if (board.blackToMove) {
                j = new Board(jumpers, 0, jumpers & board.kings, true);
            } else {
                j = new Board(0, jumpers, jumpers & board.kings, false);
            }
            out.println("These are the pieces that must jump:");
            printBoard(j);
        }

        if (PRINT_MOVES) {
            out.println("Here is the board again:");
            printBoard(board);
        }
    }

    private static final String[] LEFT_INDICES  = {
        " 1", " 5", " 9", "13", "17", "21", "25", "29"
    };
    private static final String[] RIGHT_INDICES = {
        " 4", " 8", "12", "16", "20", "24", "28", "32"
    };
    private static final char[] GAME_PIECES = {
        '-', 'b', 'w', 'g', 'K', 'B', 'W', 'G'
    };

    public static void printBoard(Board board) {
        if (PRINT_TOP_BOTTOM_NBRS) {
            out.println("       1   2   3   4");
        }

        out.print("   +-----------------+");

        if (PRINT_BITS) {
            out.print("    B W K");
        }

        out.println();

        for (int row = 0; row < 8; row++) {
            boolean evenRow = (row & 1) == 0;
            int rowOffset = row * 4;

            out.print(LEFT_INDICES[row]);
            out.print(" | ");

            if (evenRow) {
                out.print("  ");
            }

            for (int col = 0; col < 4; col++) {
                if (col != 0) {
                    out.print("   ");
                }

                int bitNbr = rowOffset + col;
                out.print(gamePiece(board, bitNbr));
            }

            if (!evenRow) {
                out.print("  ");
            }

            out.print(" | ");
            out.print(RIGHT_INDICES[row]);

            if (PRINT_BITS) {
                out.printf(" %X", (board.black >>> rowOffset) & 0xF);
                out.printf(" %X", (board.white >>> rowOffset) & 0xF);
                out.printf(" %X", (board.kings >>> rowOffset) & 0xF);
            }

            out.println();
        }

        out.println("   +-----------------+");

        if (PRINT_TOP_BOTTOM_NBRS) {
            out.println("    29  30  31  32");
        }

        //out.println();
    }

    public static char gamePiece(Board board, int bitNbr) {
        int blackBit = (board.black >>> bitNbr) & 1;
        int whiteBit = (board.white >>> bitNbr) & 1;
        int kingBit  = (board.kings >>> bitNbr) & 1;
        int piece = (kingBit << 2) | (whiteBit << 1) | blackBit;

        return GAME_PIECES[piece];
    }
}
